﻿using System;
using System.Activities;
using System.IO;
using Reactor.Configuration;
using Reactor.Environment;
using Reactor.ServiceGrid.FileSystem;
using Reactor.ServiceGrid.Packages;
using Reactor.ServiceGrid.Repositories;
using Reactor.ServiceGrid.Resources;
using Reactor.Workflow.Activities;

namespace Reactor.ServiceGrid.Workflows.Activities
{
    public class InstallServicePackage : ReactorCodeActivityBase
    {
        [RequiredArgument]
        public InArgument<Guid> ServiceInstanceIdentifier { get; set; }

        [RequiredArgument]
        public InArgument<ServiceIdentifier> ServiceIdentifier { get; set; }

        public OutArgument<string> ServiceExecutablePath { get; set; }

        private Guid _serviceInstanceIdentifier;
        private IPackage _servicePackage;
        private ServiceIdentifier _serviceIdentifier;
        private IFileSystem _serviceInstallFilesystem;
        private string _configFilePath;
        private CodeActivityContext _context;
        private const string BlankConfigurationFileContents = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><configuration><appSettings></appSettings><connectionStrings></connectionStrings></configuration>";

        #region Overrides of CodeActivity

        /// <summary>
        /// When implemented in a derived class, performs the execution of the activity.
        /// </summary>
        /// <param name="context">The execution context under which the activity executes.</param>
        protected override void Execute(CodeActivityContext context)
        {
            _context = context;
            _serviceInstanceIdentifier = context.GetValue(ServiceInstanceIdentifier);
            _serviceIdentifier = context.GetValue(ServiceIdentifier);

            ObtainPackage();
            ExtractPackage();
            ValidateExecutableAndConfigurationFile();
            ProvideConfigurationValues();
        }

        #endregion

        private void ObtainPackage()
        {
            if (Log.IsDebugEnabled) Log.DebugFormat("Attempting to find ServicePackage: {0}", _serviceIdentifier);

            _servicePackage = Context.GridContext.ServicePackageRepository.FindPackage(_serviceIdentifier.Name, _serviceIdentifier.Version.ToString());

            if (_servicePackage == null)
                throw new ApplicationException(string.Format("ServicePackage '{0}' could not be found in package repository", _serviceIdentifier));
        }

        private void ExtractPackage()
        {
            var serviceInstallsRootPath = Context.Configuration.GetConfigurationValue(ServiceGridResources.Config_ServiceInstallsRootPath);

            // Create the service install root folder if nobody has yet
            if (!Directory.Exists(serviceInstallsRootPath))
                Directory.CreateDirectory(serviceInstallsRootPath);

            var installPath = Path.Combine(serviceInstallsRootPath, _serviceIdentifier.ToString());
            if (Directory.Exists(installPath))
            {
                try
                {
                    Directory.Delete(installPath, true);
                }
                catch (DirectoryNotFoundException e)
                {
                    Log.WarnFormat("Mismatch between what Directory.Exists reports and what Directory.Delete does. Error: {0}", e);
                }
            }

            _serviceInstallFilesystem = new PhysicalFileSystem(installPath);
            _serviceInstallFilesystem.AddFiles(_servicePackage.GetFiles());
        }

        private void ValidateExecutableAndConfigurationFile()
        {
            // Rename the executable to the service name and version so it will be easily identifiable
            var serviceExecutablePath = Path.Combine(_serviceInstallFilesystem.Root, "Reactor.ServiceHost.exe");
            if (!File.Exists(serviceExecutablePath))
                throw new FileNotFoundException("Service host executable not found after package extraction.");

            if (Log.IsDebugEnabled)
                Log.DebugFormat("Service executable present at: {0}", serviceExecutablePath);

            // Look for configuration file and create one if necessary
            _configFilePath = Path.Combine(_serviceInstallFilesystem.Root, "Reactor.ServiceHost.exe.config");
            if (!File.Exists(_configFilePath)) File.WriteAllText(_configFilePath, BlankConfigurationFileContents);

            _context.SetValue(ServiceExecutablePath, serviceExecutablePath);
        }

        private void ProvideConfigurationValues()
        {
            var writer = new ApplicationConfigurationWriter(_configFilePath);

            // Write service instance identifier
            writer.WriteSetting(ServiceGridResources.Config_ManagedServiceInstanceIdentifier, _serviceInstanceIdentifier.ToString());

            // Write service instance name
            writer.WriteSetting(ServiceGridResources.Config_ManagedServiceName, _serviceIdentifier.Name);

            // Write service instance version
            writer.WriteSetting(ServiceGridResources.Config_ManagedServiceVersion, _serviceIdentifier.Version.ToString());

            // Write broker connection string
            writer.WriteSetting(ServiceGridResources.Config_BrokerConnectionString, Context.ServiceBus.Configuration.ConnectionData.BrokerDetails.ToString());

            // Write database connection string
            writer.WriteConnectionString(ServiceGridResources.Config_ReactorServices, Context.ServiceBus.Configuration.ConnectionData.DatabaseConnectionString);

            Log.Debug("Copied AppSettings and ConnectionString to guest service's configuration file.");
        }
    }
}
